package com.foodev.maven.gwt;

import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedList;
import java.util.List;

/**
 * Makes sure the gwt-dev and gwt-user jars are last in the URLClassLoader that starts the shell. This is needed since
 * gwt-dev is packed with 3rd part libraries that are common to have in the local project. This results in version
 * conflicts if a package of a different version is present in the project. This fix at least ensures the project local
 * version is preferred to the gwt-dev bundled one (since URLClassLoader searches for classes in the order of the jars).
 * There's still the very obvious risk that a local project will have a library dependency that is incompatible with one
 * needed by for instance jetty, in which case we're shafted.
 * 
 * @author Martin Algesten
 * 
 */
public class ClasspathFixShellLauncher {

    /**
     * The jars containing these files will be separated out and put last.
     */
    private final static String[] gwtBootstrapJarResources = { "com/google/gwt/dev/HostedMode.class", // gwt-dev
            "com/google/gwt/core/client/GWT.class", // gwt-user
            "com/foodev/maven/gwt/MushedClasspathJettyLauncher.class" }; // plugin

    /**
     * The bootstrapping class loader.
     */
    private static URLClassLoader globalClassLoader;


    public static void main(String[] args) {

        List<URL> classpath = getClasspathURLs();

        LinkedList<URL> gwtBootstrapJars = new LinkedList<URL>();

        for (String resource : gwtBootstrapJarResources) {
            gwtBootstrapJars.add(getJarForResource(resource));
        }

        LinkedList<URL> globalJars = new LinkedList<URL>(classpath);
        globalJars.removeAll(gwtBootstrapJars);

        globalJars.addAll(gwtBootstrapJars);

        globalClassLoader = new URLClassLoader(globalJars.toArray(new URL[globalJars.size()]), null);

        Thread.currentThread().setContextClassLoader(globalClassLoader);

        try {

            Class<?> hosted = globalClassLoader.loadClass("com.google.gwt.dev.HostedMode");

            Method main = hosted.getMethod("main", String[].class);

            Object[] invokeArgs = new Object[] { args };

            main.invoke(null, invokeArgs);

        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

    }


    /**
     * Helper method that finds the jar-file that contains the resource specified.
     * 
     * @param resource
     *            The resource to find the containing jar for.
     * @return The URL to the corresponding jar.
     */
    private static URL getJarForResource(String resource) {

        URL url = Thread.currentThread().getContextClassLoader().getResource(resource);

        if (url == null)
            throw new RuntimeException("No jar found on classpath for: " + resource);

        String s = url.toExternalForm();

        if (!s.startsWith("jar:file:"))
            throw new RuntimeException("URL doesn't start jar:file: " + s);

        if (s.indexOf("!") < 0)
            throw new RuntimeException("No '!' in jar URL: " + s);

        s = s.substring(4).split("!")[0];

        try {
            return new URL(s);
        } catch (MalformedURLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

    }


    /**
     * Returns all URL found in the CLASSPATH.
     */
    private static List<URL> getClasspathURLs() {

        String cp = System.getenv("CLASSPATH");

        String[] parts = cp.split(":");

        LinkedList<URL> urls = new LinkedList<URL>();

        for (int i = 0; i < parts.length; i++) {

            File f = new File(parts[i]);

            try {
                URL url = f.toURL();

                if (url == null) {
                    System.out.println("WARNING, null URL for: " + parts[i]);
                    continue;
                } else {
                    urls.add(url);
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }

        return urls;

    }

}
